package com.dingnate.framework.springmvn;

import com.alibaba.fastjson.JSON;
import com.dingnate.framework.springmvn.annotation.GPController;
import com.dingnate.framework.springmvn.annotation.GPRequestMapping;
import com.dingnate.framework.springmvn.annotation.GPRequestParam;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class GPDispatchServlet extends HttpServlet {

    public static final String CONTEXT_CONFIG_LOCATION = "contextConfigLocation";
    public static final String TEMPLATE_ROOT = "templateRoot";

    private Map<String, Handler> handlerMappigs = new HashMap();
    private Map<Pattern, Handler> handlerPatternMappigs = new HashMap();
    private Map<Handler, HandlerAdapter> haMappigs = new HashMap();
    private Map<String, ViewResolver> viewResolverMappings = new HashMap<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        GPApplicationContext context = new GPApplicationContext(config.getInitParameter(CONTEXT_CONFIG_LOCATION));
        Map<String, Object> ioc = context.getAll();
        System.out.println(ioc);

        initMultiPartResolver(context);

        initLocaleResolver(context);

        initThemeResolver(context);

        initHandlerMappings(context);

        initHandlerAdapters(context);

        initHandlerExceptionResolver(context);

        initRequestToViewNameTranslator(context);

        initViewResolvers(context);

        initFlashManager(context);

        System.out.println("GPSpringMVN is init.");
    }

    private void initFlashManager(GPApplicationContext context) {
    }

    private void initViewResolvers(GPApplicationContext context) {
        String templateRoot = context.getConfig().getProperty(TEMPLATE_ROOT);
        String rootPath = getClass().getClassLoader().getResource(templateRoot).getFile();

        File rootDir = new File(rootPath);
        for (File template : rootDir.listFiles()) {
            viewResolverMappings.put(template.getName(), new ViewResolver(template.getName(), template));
        }
    }

    private void initRequestToViewNameTranslator(GPApplicationContext context) {
    }

    private void initHandlerExceptionResolver(GPApplicationContext context) {
    }

    private void initHandlerAdapters(GPApplicationContext context) {
        for (Handler h : handlerMappigs.values()) {
            doInitHandlerAdapter(h);
        }
        for (Handler h : handlerPatternMappigs.values()) {
            doInitHandlerAdapter(h);
        }
    }

    private void doInitHandlerAdapter(Handler h) {
        Map<String, Integer> paramIndexMapping = new HashMap<>();
        HandlerAdapter ha = new HandlerAdapter(paramIndexMapping);
        haMappigs.put(h, ha);
        Parameter[] parameters = h.method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter param = parameters[i];
            GPRequestParam ann = param.getAnnotation(GPRequestParam.class);
            String paramName;
            if (ann == null) {
                Class<?> type = param.getType();
                if (type.equals(HttpServletRequest.class) || type.equals(HttpServletResponse.class)) {
                    paramName = type.getName();
                } else {
                    continue;
                }
            } else {
                paramName = ann.value();
            }

            paramIndexMapping.put(paramName, i);
        }
    }

    private void initHandlerMappings(GPApplicationContext context) {
        Map<String, Object> all = context.getAll();
        if (all.isEmpty()) {
            return;
        }
        for (Object value : all.values()) {
            Class<?> clazz = value.getClass();
            if (!clazz.isAnnotationPresent(GPController.class)) {
                continue;
            }

            String url = "";
            if (clazz.isAnnotationPresent(GPRequestMapping.class)) {
                url = clazz.getAnnotation(GPRequestMapping.class).value();
            }

            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                GPRequestMapping ann = method.getAnnotation(GPRequestMapping.class);
                if (ann == null) {
                    continue;
                }
                String methodUrl = ann.value();
                String handleUrl = url + methodUrl;
                if (ann.isRegex()) {
                    handlerPatternMappigs.put(Pattern.compile(handleUrl), new Handler(value, method));
                } else {

                    handlerMappigs.put(handleUrl, new Handler(value, method));
                }
                System.out.println("Mapping:" + handleUrl + "-->" + method.toGenericString());
            }

        }
    }

    private void initThemeResolver(GPApplicationContext context) {
    }

    private void initLocaleResolver(GPApplicationContext context) {
    }

    private void initMultiPartResolver(GPApplicationContext context) {
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("调用");
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            resp.getWriter().write("500 Exception, Msg:" + Arrays.toString(e.getStackTrace()));
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        Handler handler = getHander(req);
        if (handler == null) {
            resp.getWriter().write("404 not find");
            return;
        }
        HandlerAdapter ha = getHandlerAdapter(handler);
        GPModelAndView mv = ha.handle(req, resp, handler);

        applyDefaultViewName(resp, mv);
        //TODO
    }

    private void applyDefaultViewName(HttpServletResponse resp, GPModelAndView mv) {
        if (mv == null) {
            return;
        }
        ViewResolver viewResolver = viewResolverMappings.get(mv.getView());
        if (viewResolver == null) {
            return;
        }

        try {
            String result = viewResolver.parse(mv);
            if (result != null) {
                resp.getWriter().write(result);
            }
        } catch (Exception e) {
            try {
                resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                resp.getWriter().write(Arrays.toString(e.getStackTrace()));
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    private HandlerAdapter getHandlerAdapter(Handler handler) {
        return haMappigs.get(handler);
    }

    private Handler getHander(HttpServletRequest req) {
        String urlPath = req.getRequestURI().substring(req.getContextPath().length()).replace("/+", "/");
        Handler handler = handlerMappigs.get(urlPath);
        if (handler == null) {
            for (Map.Entry<Pattern, Handler> entry : handlerPatternMappigs.entrySet()) {
                if (entry.getKey().matcher(urlPath).matches()) {
                    handler = entry.getValue();
                    break;
                }
            }
        }
        return handler;
    }

    public class Handler {
        private final Method method;
        private final Object controller;

        public Handler(Object value, Method method) {
            this.controller = value;
            this.method = method;
        }
    }

    /**
     * 方法适配器
     */
    public class HandlerAdapter {
        Map<String, Integer> paramIndexMapping;

        public HandlerAdapter(Map<String, Integer> paramIndexMapping) {
            this.paramIndexMapping = paramIndexMapping;
        }

        public GPModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Handler h) throws Exception {
            Class<?>[] parameterTypes = h.method.getParameterTypes();
            Object[] args = new Object[parameterTypes.length];
            Map<String, String[]> parameterMap = req.getParameterMap();
            for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                Integer index = paramIndexMapping.get(entry.getKey());
                if (index == null) {
                    continue;
                }
                if (entry.getValue() == null || entry.getValue().length == 0) {
                    continue;
                }
                args[index] = castString2Value(Arrays.toString(entry.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ","), parameterTypes[index]);
            }

            Integer index = paramIndexMapping.get(HttpServletRequest.class);
            if (index != null) {
                args[index] = req;
            }
            index = paramIndexMapping.get(HttpServletResponse.class);
            if (index != null) {
                args[index] = resp;
            }
            return (GPModelAndView) h.method.invoke(h.controller, args);
//            Object result = h.method.invoke(h.controller, args);
//            resp.setStatus(HttpServletResponse.SC_OK);
//            resp.getWriter().write(castValue2String(result));

        }

        private String castValue2String(Object result) {
            if (result == null) {
                return "";
            }
            return JSON.toJSONString(result);
        }

        private Object castString2Value(String value, Class<?> parameterType) {
            if (parameterType.equals(String.class)) {
                return value;
            }
            return JSON.parseObject(value, parameterType);
        }
    }

    private class ViewResolver {
        public String getViewName() {
            return viewName;
        }

        String viewName;
        File template;

        public ViewResolver(String viewName, File template) {
            this.viewName = viewName;
            this.template = template;
        }

        public String parse(GPModelAndView mv) throws Exception {
            StringBuilder sb = new StringBuilder();
            try (RandomAccessFile ra = new RandomAccessFile(template, "r")) {
                String line = null;
                while (null != (line = ra.readLine())) {
                    Matcher matcher = matcher(line);
                    if (matcher.find()) {
                        for (int i = 1; i <= matcher.groupCount(); i++) {
                            String paramName = matcher.group(i);
                            Object paramValue = mv.getModel().get(paramName);
                            if (paramValue == null) {
                                continue;
                            }
                            line = line.replace(String.format("@{%s}", paramName), String.valueOf(paramValue));
                        }

                    }
                    sb.append(line).append("\n");
                }
            }
            return sb.toString();
        }

        Matcher matcher(String input) {
            return Pattern.compile("@\\{(.+?)\\}", Pattern.CASE_INSENSITIVE).matcher(input);
        }
    }
}
